package com.example.socket.handler;

import com.example.socket.anno.Async;
import com.example.socket.anno.Noreply;
import com.example.socket.anno.SocketPush;
import com.example.socket.anno.Sync;
import com.example.socket.anno.impl.BodyParameter;
import com.example.socket.anno.impl.CallbackParameter;
import com.example.socket.anno.impl.RequestParameter;
import com.example.socket.core.Request;
import com.example.socket.core.Response;
import com.example.socket.core.Session;
import com.example.socket.parameter.Parameter;
import com.example.socket.parameter.ResultCallback;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * 注释方法的消息体定义
 */
public class MethodDefinition extends TypeDefinition {

    /** 构造方法 */
    public static MethodDefinition valueOf(byte format, Class<?> clz, Method method, ParameterBuilder builder) {
        Parameter[] parameters = builder.buildParameters(method);
        Type request = findRequestType(method, parameters);
        boolean callback = findCallbackExist(parameters);
        Type response = findResponseType(method, callback, parameters);

        MethodDefinition result = new MethodDefinition();
        result.format = format;
        result.clazz = clz;
        result.method = method;
        result.parameters = parameters;
        result.request = request;
        result.response = response;
        result.callback = callback;

        // 方法为同步执行
        Sync sync = method.getAnnotation(Sync.class);
        if (sync != null) {
            result.syncKey = sync.value();
        }
        // 方法存在异步回调
        Async async = method.getAnnotation(Async.class);
        if (async != null) {
            result.async = true;
        }
        // 允许延期推送
        SocketPush delayed = clz.getAnnotation(SocketPush.class);
        if (delayed != null && delayed.delayed()) {
            result.delayed = delayed.delayed();
        }
        Noreply noreply = method.getAnnotation(Noreply.class);
        if (noreply != null) {
            result.noreply = true;
        }

        return result;
    }

    /**
     * 查找请求信息体类型
     * @param parameters
     * @return
     */
    private static boolean findCallbackExist(Parameter[] parameters) {
        if (parameters.length == 0) {
            return false;
        }
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p instanceof CallbackParameter) {
                return true;
            }
        }
        return false;
    }

    /**
     * 查找请求信息体类型
     * @param parameters
     * @return
     */
    private static Type findRequestType(Method method, Parameter[] parameters) {
        if (parameters.length == 0) {
            return void.class;
        }

        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p instanceof BodyParameter) {
                return method.getGenericParameterTypes()[i];
            }
            if (p instanceof RequestParameter) {
                Type type = method.getGenericParameterTypes()[i];
                if (type instanceof ParameterizedType) {
                    return ((ParameterizedType) type).getActualTypeArguments()[0];
                }
                return Object.class;
            }
        }
        return null;
    }

    /**
     * 查找回应信息体类型
     * @param method
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static Type findResponseType(Method method, boolean callback, Parameter[] parameters) {
        if (callback) {
            for (int i = 0; i < parameters.length; i++) {
                Parameter p = parameters[i];
                if (p instanceof CallbackParameter) {
                    Type parameterType = method.getGenericParameterTypes()[i];
                    if (parameterType instanceof Class && ResultCallback.class.isAssignableFrom((Class) parameterType)) {
                        Class clazz = (Class) parameterType;
                        Type[] superInterfaces = clazz.getGenericInterfaces();
                        for (Type type : superInterfaces) {
                            if (type instanceof ParameterizedType && ResultCallback.class.isAssignableFrom((Class) ((ParameterizedType) type).getRawType())) {
                                ParameterizedType parameterizedType = (ParameterizedType) parameterType;
                                return parameterizedType.getActualTypeArguments()[0];
                            }
                        }
                    } else if (parameterType instanceof ParameterizedType && ResultCallback.class.isAssignableFrom((Class) ((ParameterizedType) parameterType).getRawType())) {
                        ParameterizedType parameterizedType = (ParameterizedType) parameterType;
                        return parameterizedType.getActualTypeArguments()[0];
                    }
                }
            }
            return null;
        }

        Type type = method.getGenericReturnType();
        if (type.equals(Response.class)) {
            return Object.class;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            if (pType.getRawType().equals(Response.class)) {
                return pType.getActualTypeArguments()[0];
            }
        }
        return type;
    }

    private Class<?> clazz;
    private Method method;
    private Parameter[] parameters;
    private boolean callback;

    /**
     * 创建请求参数数组
     * @param request
     * @param session
     * @param callback
     * @return
     */
    public Object[] buildParameters(Request<?> request, Session session, ResultCallback<?> callback) {
        Object[] result = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            result[i] = p.getValue(request, session, callback);
        }
        return result;
    }

    // Getter and Setter ...

    public Class<?> getClazz() {
        return clazz;
    }

    public Method getMethod() {
        return method;
    }

    public boolean isCallback() {
        return callback;
    }

}
